package no.sachmuld.methodcombination.api.adapter;

import no.sachmuld.methodcombination.api.BehaviourChain;
import no.sachmuld.methodcombination.api.Direction;
import no.sachmuld.methodcombination.api.MethodInvocation;
import no.sachmuld.methodcombination.impl.AbstractMethodInvocation;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

/**
 *
 */
public abstract class Simple<B, R, T> {

    private BehaviourChain<B> chain;
    private Direction direction = Direction.BOTTOM_UP;

    public Simple(BehaviourChain<B> chain) {
        this.chain = chain;
    }

    public R execute() {

        R result = new AbstractMethodInvocation<B, R>(direction == Direction.BOTTOM_UP ? chain.bottomUp() : chain.topDown(), null, null, null) {
            @Override
            protected R executeBehaviour(B behaviour) {
                return executeAround(behaviour, this);
            }

            @Override
            protected R atEnd() {
                return executePrimaryMethods();
            }
        }.execute();

        return result;
    }

    public Simple<B, R, T> setDirection(Direction direction) {
        this.direction = direction;
        return this;
    }

    private R executePrimaryMethods() {

        for (B behaviour : direction == Direction.BOTTOM_UP ? chain.bottomUp() : chain.topDown()) {
            T result = executePrimary(behaviour);
            boolean doContinue = handleSingleResult(result);
            if (!doContinue) {
                break;
            }
        }
        return combinedResult();
    }

    protected R executeAround(B behaviour, MethodInvocation<R> methodInvocation) {
        return methodInvocation.proceed();
    }
    protected abstract T executePrimary(B behaviour);
    protected abstract boolean handleSingleResult(T singleResult);
    protected abstract R combinedResult();

    static public abstract class Or<B> extends Simple<B, Boolean, Boolean> {

        private Boolean combinedResult;

        protected Or(BehaviourChain<B> bBehaviourChain) {
            super(bBehaviourChain);
        }

        @Override
        protected boolean handleSingleResult(Boolean singleResult) {
            if (combinedResult == null) {
                combinedResult = singleResult;
            } else {
                combinedResult = combinedResult || singleResult;
            }
            return !combinedResult;
        }

        @Override
        protected Boolean combinedResult() {
            return combinedResult;
        }
    }

    static public abstract class And<B> extends Simple<B, Boolean, Boolean> {

        private Boolean combinedResult;

        protected And(BehaviourChain<B> bBehaviourChain) {
            super(bBehaviourChain);
        }

        @Override
        protected boolean handleSingleResult(Boolean singleResult) {
            if (combinedResult == null) {
                combinedResult = singleResult;
            } else {
                combinedResult = combinedResult && singleResult;
            }
            return combinedResult;
        }

        @Override
        protected Boolean combinedResult() {
            return combinedResult;
        }
    }

    static public abstract class List <B, E> extends Simple<B, java.util.List<E>, E> {

        private java.util.List<E> combinedResult = new ArrayList<>();

        public List(BehaviourChain<B> bBehaviourChain) {
            super(bBehaviourChain);
        }

        @Override
        protected boolean handleSingleResult(E singleResult) {
            combinedResult.add(singleResult);
            return true;
        }

        @Override
        protected java.util.List<E> combinedResult() {
            return combinedResult;
        }
    }

    static public abstract class Append <B, E> extends Simple<B, java.util.List<E>, java.util.List<E>> {

        private java.util.List<E> combinedResult = new ArrayList<>();

        protected Append(BehaviourChain<B> bBehaviourChain) {
            super(bBehaviourChain);
        }

        @Override
        protected boolean handleSingleResult(java.util.List<E> singleResult) {
            combinedResult.addAll(singleResult);
            return true;
        }

        @Override
        protected java.util.List<E> combinedResult() {
            return combinedResult;
        }
    }

    static public abstract class All <B> extends Simple<B, Void, Void> {

        protected All(BehaviourChain<B> bBehaviourChain) {
            super(bBehaviourChain);
        }

        @Override
        protected boolean handleSingleResult(Void singleResult) {
            return true;
        }

        @Override
        protected Void combinedResult() {
            return null;
        }
    }

    static public abstract class Sum<B, T extends Number> extends Simple<B, T, T> {

        private T sum;
        private Adder<T> adder;

        public interface Adder <T> {
            T add(T t1, T t2);
        }

        protected Sum(BehaviourChain<B> behaviourChain, Class<T> returnType) {
            this(behaviourChain, createAdder(returnType));
        }

        protected Sum(BehaviourChain<B> bBehaviourChain, Adder<T> adder) {
            super(bBehaviourChain);
            this.adder = adder;
        }

        @Override
        protected boolean handleSingleResult(T singleResult) {
            if (sum == null) {
                sum = singleResult;
            } else {
                sum = adder.add(sum, singleResult);
            }
            return true;
        }

        static private Adder createAdder(Class returnType) {
            if (returnType == Integer.TYPE || returnType == Integer.class) {
                return new Adder<Integer>() {
                    @Override
                    public Integer add(Integer t1, Integer t2) {
                        return t1 + t2;
                    }
                };
            } else if (returnType == BigInteger.class) {
                 return new Adder<BigInteger>() {
                    @Override
                    public BigInteger add(BigInteger t1, BigInteger t2) {
                        return t1.add(t2);
                    }
                };
            } else {
                throw new RuntimeException("Unsupported Number type: " + returnType.getName());
            }
        }

        @Override
        protected T combinedResult() {
            return sum;
        }
    }

    static public abstract class Min <B, T extends Comparable> extends Simple<B, T, T> {

        private T min;

        protected Min(BehaviourChain<B> bBehaviourChain) {
            super(bBehaviourChain);
        }

        @Override
        protected boolean handleSingleResult(T singleResult) {
            if (min == null) {
                min = singleResult;
            } else {
                int compare = singleResult.compareTo(min);
                if (compare < 0) {
                    min = singleResult;
                }
            }
            return true;
        }

        @Override
        protected T combinedResult() {
            return min;
        }
    }

    static public abstract class Max <B, T extends Comparable> extends Simple<B, T, T> {

        private T max;

        protected Max(BehaviourChain<B> bBehaviourChain) {
            super(bBehaviourChain);
        }

        @Override
        protected boolean handleSingleResult(T singleResult) {
            if (max == null) {
                max = singleResult;
            } else {
                int compare = singleResult.compareTo(max);
                if (compare > 0) {
                    max = singleResult;
                }
            }
            return true;
        }

        @Override
        protected T combinedResult() {
            return max;
        }
    }


}
